package com.test.service.impl;

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.test.entity.Const;
import com.test.entity.dto.*;
import com.test.entity.vo.request.CommentVo;
import com.test.entity.vo.request.TopicCreateVo;
import com.test.entity.vo.request.UpdateTopicVo;
import com.test.entity.vo.response.TopTopicVo;
import com.test.entity.vo.response.TopicComment;
import com.test.entity.vo.response.TopicDetailVo;
import com.test.entity.vo.response.TopicPreviewVo;
import com.test.mapper.*;
import com.test.service.NotificationService;
import com.test.service.TopicService;
import com.test.service.TypeService;
import com.test.utils.CacheUtils;
import com.test.utils.FlowUtils;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

@Service
@Slf4j
public class TopicServiceImpl extends ServiceImpl<TopicMapper, Topic> implements TopicService {
    @Resource
    TypeService typeService;
    @Resource
    CommentMapper commentMapper;
    @Resource
    CacheUtils cacheUtils;
    @Resource
    FlowUtils utils;
    @Resource
    AccountMapper accountMapper;
    @Resource
    TypeMapper typeMapper;
    @Resource
    TopicMapper topicMapper;
    @Resource
    AccountPrivacyMapper accountPrivacyMapper;
    @Resource
    AccountDetailsMapper accountDetailsMapper;
    @Resource
    StringRedisTemplate template;
    @Resource
    NotificationMapper notificationMapper;
    @Resource
    NotificationService notificationService;
  public List<Type> getTypeList()
  {
    return typeService.getTypeList();
  }

    @Override
    public String createTopic(int uid, TopicCreateVo vo) {
    if(!limitTextNumber(vo.getContent())) return "文本长度超过限制";
    int type = vo.getType();
    List<Type> typeList = this.getTypeList();
    if(typeList.stream().noneMatch(t->t.getId()==type)) return "类型不存在";
    String key = Const.FLOW_TOPIC_COUNT+uid;
    if(!utils.limitPeriodCounterCheck(key,100,3600)) return "发布文章次数过多";
    Topic topic = new Topic();
    BeanUtils.copyProperties(vo,topic);
    topic.setUid(uid);
    topic.setContent(vo.getContent().toJSONString());
    topic.setTime(new Date());
    log.info("发布文章");
    if(save(topic)){
        cacheUtils.deleteFromCache(key);
        return null;
    }
    return "发布文章失败";

    }

  @Override
  public List<TopicPreviewVo> getListTopic(int type, int pageNumber) {
      String key = Const.CACHE_TOPIC_NUMBER+type+":"+pageNumber;
      List<TopicPreviewVo> list = cacheUtils.getListFromCache(key,TopicPreviewVo.class);
      if(list != null){
          return list;
      }
      List<Topic> topicList ;
      Page<Topic> page = Page.of(pageNumber,10);
    if(type == 0) {
         topicMapper.selectPage(page, Wrappers.<Topic>query().orderByDesc("time"));
    }
    else {
        topicMapper.selectPage(page, Wrappers.<Topic>query().eq("type",type).orderByDesc("time"));
    }
    topicList = page.getRecords();
    if(topicList.isEmpty()) return null;
    list = topicList.stream().map(this::resolveTopic).toList();
    cacheUtils.saveListToCache(key,list,60);
    return list;

  }

    @Override
    public List<TopTopicVo> getTopTopic() {
      List<Topic> topic = topicMapper.selectList(new QueryWrapper<Topic>().select("id","title","time").eq("top",1));
      return topic.stream().map(t->{
          TopTopicVo vo = new TopTopicVo();
          BeanUtils.copyProperties(t,vo);
          return vo;
      }).toList();
    }

    @Override
    public TopicDetailVo getTopic(int tid,int id) {
      //获取到数据库中的帖子基本信息，然后通过隐私过滤
        Topic topic = topicMapper.selectById(tid);
        System.out.println(topic);
        TopicDetailVo vo = new TopicDetailVo();
        BeanUtils.copyProperties(topic,vo);
        TopicDetailVo.User user = new TopicDetailVo.User();
        System.out.println(tid);
        TopicDetailVo.Interact interact = new TopicDetailVo.Interact(
                hasInteract(tid,id,"like"),
                hasInteract(tid,id,"collect")
        );
        vo.setCommentCount(commentMapper.selectCount(new QueryWrapper<Comment>().eq("tid",tid)));
        System.out.println(interact);
        vo.setInteract(interact);

        user = fillPrivacyDetail(user,topic.getUid());
        vo.setUser(user);
        return vo;


    }

    /**
     * 交互操作的方法
     * 该方法用于处理交互对象的更新，根据交互类型同步更新到缓存中，并安排交互的保存计划
     *
     * @param interact 交互对象，包含交互的类型和内容
     * @param status 交互的状态，表示交互是否成功
     */
    @Override
    public void Interact(Interact interact, Boolean status) {
        // 获取交互的类型，用于后续的同步操作
        String type = interact.getType();
        // 使用字符串的intern方法确保类型字符串在常量池中是唯一的，从而确保同一类型的交互操作是同步的
        synchronized (type.intern()){
            // 使用Redis模板将交互的状态更新到Hash结构中，键为交互类型和具体键，值为交互状态
            template.opsForHash().put(type,interact.toKey(),Boolean.toString(status));
            // 调用保存交互调度的方法，安排保存交互数据的任务
            this.saveInteractSchedule(type);
        }
    }

    @Override
    public List<TopicPreviewVo> getCollects(int id) {
        return baseMapper.getCollects(id).stream().map(topic -> {
            TopicPreviewVo vo = resolveTopic(topic);
            BeanUtils.copyProperties(topic,vo);
            return vo;
        }).toList();
    }

@Override
public String topicUpdate(int uid, UpdateTopicVo vo) {
    if (!limitTextNumber(vo.getContent())) return "文本长度超过限制";
    int type = vo.getType();
    List<Type> typeList = this.getTypeList();
    if (typeList.stream().noneMatch(t -> t.getId() == type)) return "类型不存在";

    // 构建更新条件
    UpdateWrapper<Topic> queryWrapper = Wrappers.<Topic>update()
            .eq("id", vo.getId())
            .eq("uid", uid)
            .set("content", vo.getContent().toString())
            .set("type", type)
            .set("title", vo.getTitle());

    // 执行更新操作
    boolean result = baseMapper.update(null, queryWrapper) > 0;

    // 添加日志输出
    log.info("Update result: {}", result);

    if (result) {
        // 查询更新后的数据
        Topic updatedTopic = baseMapper.selectById(vo.getId());
        log.info("Updated topic: {}", updatedTopic);
        return null;
    } else {
        return "更新失败";
    }
}

    @Override
    public String addComment(int uid, CommentVo vo) {
        if(!limitTextNumber(JSONObject.parseObject(vo.getContent()))) return "文本长度超过限制";
        String key = Const.FLOW_COMMENT_COUNT+uid;
        if(!utils.limitPeriodCounterCheck(key,2,60)) return "发布文章次数过多";
        Comment comment = new Comment();
        BeanUtils.copyProperties(vo,comment);
        comment.setUid(uid);
        comment.setTime(new Date());
        commentMapper.insert(comment);
        //分两种情况，
        // 一种是别人回复评论（这里也分两种，一种是自己回复，一种是别人回复）
        // 一种是回复帖子
        Topic topic = topicMapper.selectById(vo.getTid());
        Account account = accountMapper.selectById(uid);
        if(vo.getQuote() >0){
            if(!Objects.equals(topic.getUid(),comment.getUid())){
                notificationMapper.insert(new Notification(topic.getUid(),"你有新的帖子评论回复",account.getUsername()+"回复了你的帖子评论， 快去看看吧!","success","/index/topic-detail/"+topic.getId(),new Date()));
            }

        }
        else if(vo.getQuote() < 0)
            notificationMapper.insert(new Notification(topic.getUid(),"你有新的帖子回复",account.getUsername()+"回复了你的主题，快去看看吧!","success","/index/topic-detail/"+topic.getId(),new Date()));

        return null;
    }

    /**
     * 根据主题ID获取评论列表
     *
     * @param tid 主题ID，用于查询与该主题相关的评论
     * @param pageNumber 页码，用于分页查询，指定从哪一页开始获取评论
     * @return 返回一个TopicComment对象列表，每个对象包含评论详情及其用户信息
     */
    @Override
    public List<TopicComment> getComment(int tid,int pageNumber) {
        // 创建一个分页对象，指定当前页码和每页大小
        Page<Comment> page = Page.of(pageNumber,5);
        // 根据主题ID查询评论，并分页获取
        commentMapper.selectPage(page, Wrappers.<Comment>query().eq("tid",tid));

        // 将查询到的评论对象转换为TopicComment对象，并处理每个评论的引用和用户信息
        return page.getRecords().stream().map(c->{
            // 创建一个新的TopicComment对象
            TopicComment comment = new TopicComment();
            // 将Comment对象的属性复制到TopicComment对象中
            BeanUtils.copyProperties(c,comment);

            // 如果当前评论引用了其他评论，则获取引用评论的内容并处理
            if(c.getQuote() > 0) {
                // 根据引用ID获取评论内容
                Comment comment1 = commentMapper.selectById(c.getQuote());
                if (comment1 != null) {
                    String quote = comment1.getContent();
                    // 创建一个字符串构建器，用于存储处理后的引用评论内容预览
                    StringBuilder previewText = new StringBuilder();
                    // 解析并处理引用评论内容，这里仅做预览处理，不进行其他操作
                    this.resolvecontext(quote, previewText, a -> {
                    });
                    // 将处理后的引用评论内容设置到当前评论对象中
                    comment.setQuote(String.valueOf(previewText));
                }
                else {
                    comment.setQuote("该评论已被删除");
                }
            }

            // 创建一个新的用户对象，用于存储评论用户的隐私信息
            TopicComment.User user = new TopicComment.User();
            // 填充用户隐私信息，并将其设置到评论对象中
            user = fillPrivacyDetail(user,c.getUid());
            comment.setUser(user);

            // 返回处理后的评论对象
            return comment;
        }).toList();
    }

    @Override
    public Void deleteComment(int id, int uid) {
        commentMapper.delete(Wrappers.<Comment>query().eq("id",id).eq("uid",uid));
        return null;
    }


    private boolean hasInteract(int tid,int uid,String type){
        String key = tid +":"+ uid;
        if(template.opsForHash().hasKey(type,key)){
            return Boolean.parseBoolean(template.opsForHash().get(type,key).toString());
        }
        return baseMapper.getInteractStatus(tid,uid,type ) > 0;
    }
    private final Map<String,Boolean> state = new HashMap<>();
    ScheduledExecutorService service = Executors.newScheduledThreadPool(2);

  /**
   * 根据类型保存交互数据
   * 此方法避免了同一类型数据的重复保存，通过在保存前检查当前类型的数据是否已经计划保存
   * 如果未计划保存，则安排一个任务在未来保存数据，并在保存后重置保存状态
   *
   * @param type 交互数据的类型，用于区分不同的交互数据
   */
  public void saveInteractSchedule(String type){
      // 检查给定类型的数据是否已经计划保存
      if(!state.getOrDefault(type,false)){
          // 标记当前类型的数据为已计划保存
          state.put(type,true);
          // 使用schedule服务安排一个任务在未来执行，延迟3秒后保存交互数据
          service.schedule(() ->{
              // 保存指定类型的交互数据
            try {
                this.saveInteract(type);
            }
            catch (Exception e){
                e.printStackTrace();
            }
              // 重置当前类型的数据保存状态，以便下一次保存
              state.put(type,false);
          },3, TimeUnit.SECONDS);
      }
  }
    private void saveInteract(String type){
      synchronized (type.intern()){
          List<Interact> check = new ArrayList<>();
          List<Interact> uncheck = new ArrayList<>();
          template.opsForHash().entries(type).forEach((k,v) ->{
              if(Boolean.parseBoolean(v.toString()))
                  check.add(Interact.parse(k.toString(),type));
              else
                  uncheck.add(Interact.parse(k.toString(),type));
          });
          if(!check.isEmpty())
              baseMapper.addInteract(check,type);
          if(!uncheck.isEmpty())
              baseMapper.deleteInteract(uncheck,type);
          template.delete(type);
      }

    }

    /**
     * 填充用户隐私详情
     *
     * 本方法用于根据用户ID（tid）从数据库中获取用户账户信息、账户详细信息和账户隐私设置，
     * 并根据隐私设置中隐藏的字段，将账户信息和详细信息填充到目标对象中
     *
     * @param target 目标对象，用于接收填充的用户隐私详情
     * @param tid 用户ID，用于查询用户相关的隐私信息
     * @param <T> 目标对象的类型，允许任何类型的对象作为目标对象
     * @return 返回填充了用户隐私详情的目标对象
     */
    private <T> T fillPrivacyDetail(T target,int tid){
        // 根据用户ID获取账户信息
        Account account = accountMapper.selectById(tid);
        // 根据用户ID获取账户详细信息
        AccountDetails details = accountDetailsMapper.selectById(tid);
        // 根据用户ID获取账户隐私设置
        AccountPrivacy accountPrivacy = accountPrivacyMapper.selectById(tid);
        // 获取隐私设置中隐藏的字段列表
        String[] ignores = accountPrivacy.hiddenFileds();
        // 将账户信息复制到目标对象中，忽略隐私设置中隐藏的字段
        BeanUtils.copyProperties(account,target,ignores);
        // 将账户详细信息复制到目标对象中，忽略隐私设置中隐藏的字段
        BeanUtils.copyProperties(details,target,ignores);
        // 返回填充了用户隐私详情的目标对象
        return target;
    }

    private TopicPreviewVo resolveTopic(Topic topic){
      TopicPreviewVo vo = new TopicPreviewVo();
      BeanUtils.copyProperties(accountMapper.selectById(topic.getUid()),vo);
      BeanUtils.copyProperties(topic,vo);
      List<String> images = new ArrayList<>();
      StringBuilder previewText = new StringBuilder();
      resolvecontext(topic.getContent(),previewText,obj -> images.add(obj.toString()));
          vo.setText(previewText.length()>300 ? previewText.substring(0,300)+"..." : previewText.toString());
          vo.setImages(images);
          vo.setLike(baseMapper.getInteractCount(topic.getId(),"like"));
          vo.setCollect(baseMapper.getInteractCount(topic.getId(),"collect"));
          return vo;

      }


  private void resolvecontext(String topic, StringBuilder previewText, Consumer<Object> consumer){
      for(Object o : JSONObject.parseObject(topic).getJSONArray("ops")){
          Object insert = JSONObject.from(o).get("insert");
          if(insert instanceof String text){
              if(previewText.length()>=300) continue;
              previewText.append(text);
          }
          else if(insert instanceof Map<?,?> map){
              Optional.ofNullable(map.get("image"))
                      .ifPresent(consumer);
          }

  }
      }

  private Boolean limitTextNumber(JSONObject content){
      if(content  == null) return false;
      long count = 0;
      for(Object o : content.getJSONArray("ops")){

        count += JSONObject.from(o).getString("insert").length();
        if(count > 2000) return false;
      }

      return true;
    }
}
